package com.liyunc.demo.comp.jwt.impl;


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.liyunc.demo.comp.common.utils.DatesUtils;
import com.liyunc.demo.comp.jwt.JwtCodec;
import com.liyunc.demo.comp.jwt.JwtConfig;
import com.liyunc.demo.comp.jwt.JwtPayload;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author liyuncong
 * @version 1.0
 * @file JwtCodeImpl.java
 * @brief JwtCodeImpl
 * @details JwtCodeImpl
 * @date 2022-03-23
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2022-03-23               liyuncong        Created
 */
@Slf4j
public class JwtCodecImpl<T> implements JwtCodec<T> {

    private final ObjectMapper mapper;

    public JwtCodecImpl(ObjectMapper objectMapper) {
        this.mapper = objectMapper;
    }


    @Override
    public String encode(JwtPayload<T> payload, JwtConfig config) {
        log.debug("Trying to encode jwt with payload {} and cfg {}", payload, config);
        final LocalDateTime now = LocalDateTime.now();
        final Date issuerAt = DatesUtils.asDate(now);
        final Date expireDate = DatesUtils.asDate(now.plusSeconds(config.getExpireSeconds()));
        final Map<String, Object> header = new HashMap<>();
        header.put("alg", "HS256");
        header.put("typ", "JWT");
        try {
            final String loginUserId = payload.getLoginUserId();
            final String loginUserName = payload.getLoginUserName();
            final String properties = mapper.writeValueAsString(payload.getProperties());
            final Algorithm algorithm = Algorithm.HMAC256(config.getSecret());
            return JWT
                .create()
                .withHeader(header)
                .withClaim("loginUserId", loginUserId)
                .withClaim("loginUserName", loginUserName)
                .withClaim("properties", properties)
                .withIssuer(config.getIssuer())
                .withIssuedAt(issuerAt)
                .withExpiresAt(expireDate)
                .sign(algorithm);
        } catch (JsonProcessingException exception) {
            exception.printStackTrace();
        }
        return null;
    }

    @Override
    public JwtPayload<T> decode(String token, JwtConfig config) {
        Algorithm algorithm = Algorithm.HMAC256(config.getSecret());
        JWTVerifier verifier = JWT.require(algorithm).build();
        DecodedJWT jwt;
        jwt = verifier.verify(token);
        JwtPayload<T> payload = new JwtPayload<>();
        try {
            String loginUserId = jwt.getClaim("loginUserId").asString();
            String loginUserName = jwt.getClaim("loginUserName").asString();
            T properties = mapper.readValue(
                jwt.getClaim("properties").asString(),
                new TypeReference<>() {
                });
            payload.setLoginUserId(loginUserId);
            payload.setLoginUserName(loginUserName);
            payload.setProperties(properties);
            log.debug("Decode token payload is {}", payload);
            if (config.getValidateWhenDecode()) {
                payload.validate(config);
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return payload;
    }
}
